cmake_minimum_required(VERSION 3.8)
project(verona C CXX)

option(USE_MEASURE "Measure performance with histograms" OFF)
option(USE_SCHED_STATS "Track scheduler stats" OFF)
option(USE_ASAN "Use address sanitizer" OFF)
option(USE_QUICK_EXIT "Enable the use of quick_exit in the C++ API" ON)
option(VERONA_CI_BUILD "Disable features not sensible for CI" OFF)


set(SNMALLOC_ONLY_HEADER_LIBRARY ON)
add_subdirectory(external/snmalloc EXCLUDE_FROM_ALL)

set(default_build_type "RelWithDebInfo")
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  message(STATUS "Setting build type to '${default_build_type}' as none was specified.")
  set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE)
  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()

macro(subdirlist result curdir)
  file(GLOB children LIST_DIRECTORIES true RELATIVE ${curdir} ${curdir}/*)
  set(dirlist "")
  foreach(child ${children})
    if(IS_DIRECTORY ${curdir}/${child})
      list(APPEND dirlist ${child})
    endif()
  endforeach()
  set(${result} ${dirlist})
endmacro()

if(NOT MSVC)
  find_file(EXECINFO_H "execinfo.h")
  if(EXISTS ${EXECINFO_H})
    set(USE_EXECINFO ON)
    add_definitions(-DUSE_EXECINFO)
  endif()
endif()


add_library(verona_rt INTERFACE)
# ASAN must be added first, or it gets upset.
if(USE_ASAN)
  if(MSVC)
    message(FATAL_ERROR "MSVC does not support ASAN")
  endif()
  target_compile_definitions(verona_rt INTERFACE -DUSE_MALLOC)
  target_compile_options(verona_rt INTERFACE -g -fsanitize=address -fno-omit-frame-pointer)
  target_link_libraries(verona_rt INTERFACE -lasan)
endif()

if(USE_EXECINFO)
  if (${CMAKE_BUILD_TYPE} MATCHES "Debug|RelWithDebInfo")
    target_link_libraries(verona_rt INTERFACE -rdynamic)
  endif()

  find_library(LIBEXECINFO execinfo)
  if(EXISTS ${LIBEXECINFO})
    target_link_libraries(verona_rt INTERFACE execinfo)
  endif()
endif()

if(VERONA_CI_BUILD)
  target_compile_definitions(verona_rt INTERFACE -DCI_BUILD)
endif()

target_link_libraries(verona_rt INTERFACE snmalloc_lib)
target_include_directories(verona_rt INTERFACE .)

if(MSVC)
  target_compile_definitions(verona_rt INTERFACE -D__EXCEPTIONS)
  target_compile_options(verona_rt INTERFACE /std:c++latest /permissive-)
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
  set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG")
else()
  find_package(Threads REQUIRED)
  target_compile_options(verona_rt INTERFACE -mcx16 -march=native)
endif()

if(USE_SCHED_STATS)
  target_compile_definitions(verona_rt INTERFACE -DUSE_SCHED_STATS)
endif()

if(USE_QUICK_EXIT)
  target_compile_definitions(verona_rt INTERFACE -DUSE_QUICK_EXIT)
endif()

target_compile_definitions(verona_rt INTERFACE -DSNMALLOC_CHEAP_CHECKS)

set(CMAKE_CXX_STANDARD 17)


warnings_high()

# To build with just the header library target define SNMALLOC_ONLY_HEADER_LIBRARY
# in containing Cmake file.
if(NOT DEFINED VERONA_RT_ONLY_HEADER_LIBRARY)

  if(MSVC)
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
    set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG")
  endif()

  if(VERONA_CI_BUILD)
    if(MSVC)
      set (CON_CORES 2)
    else()
      set (CON_CORES 2 3)
    endif()
  else()
    include(ProcessorCount)
    ProcessorCount(N)
    set (CON_CORES 2 ${N})
  endif()

  enable_testing()

  set(TESTDIR ${CMAKE_CURRENT_SOURCE_DIR}/test)
  subdirlist(TEST_CATEGORIES ${TESTDIR})
  foreach(TEST_CATEGORY ${TEST_CATEGORIES})
    foreach(TEST_MODE "sys" "con")
      subdirlist(TESTS ${TESTDIR}/${TEST_CATEGORY})
      foreach(TEST ${TESTS})
        unset(SRC)
        aux_source_directory(${TESTDIR}/${TEST_CATEGORY}/${TEST} SRC)
        set(TESTNAME "${TEST_CATEGORY}-${TEST_MODE}-${TEST}")
        add_executable(${TESTNAME} ${SRC})
        target_link_libraries(${TESTNAME} verona_rt)
        if (${TEST_MODE} STREQUAL "sys")
          target_compile_definitions(${TESTNAME} PRIVATE USE_SYSTEMATIC_TESTING)
        else()
          add_test(${TESTNAME} ${TESTNAME})
          target_compile_definitions(${TESTNAME} PRIVATE USE_FLIGHT_RECORDER)
        endif ()
        endforeach()
    endforeach()
  endforeach()

  # Try to avoid testing fairness of OS.
  set_tests_properties(func-con-fair_variance PROPERTIES PROCESSORS 7)

  MATH(EXPR CHUNK "500")

  # cowngc{1,4} are basically variations on the same test.
  # The other cowngc tests are more complicated.
  foreach(VARIANT 1 4)
    foreach(CORES 2 3 8)
      foreach(FORWARD 10)
        foreach(RING 0 1 2 10)
          foreach(SEED RANGE 1 20)
            MATH(EXPR SEEDLOWER "${SEED} * ${CHUNK}")
            MATH(EXPR SEEDUPPER "((${SEED} + 1) * ${CHUNK}) - 1")
            SET (TESTNAME "func-sys-cowngc${VARIANT}_${CORES}_${FORWARD}_${RING}_${SEEDLOWER}")
            add_test(${TESTNAME} func-sys-cowngc${VARIANT} --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER} --forward ${FORWARD} --ring ${RING})
          endforeach()
        endforeach()
      endforeach()
    endforeach()
  endforeach()

  foreach(VARIANT 1 4)
    foreach(CORES ${CON_CORES})
      foreach(FORWARD 10)
        foreach(RING 0 1 2 10)
          foreach(SEED RANGE 1 2)
            MATH(EXPR SEEDLOWER "${SEED} * 10")
            MATH(EXPR SEEDUPPER "((${SEED} + 1) * 10) - 1")
            SET (TESTNAME "func-con-cowngc${VARIANT}_${CORES}_${FORWARD}_${RING}_${SEEDLOWER}")
            add_test(${TESTNAME} func-con-cowngc${VARIANT} --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER} --forward ${FORWARD} --ring ${RING})
            set_tests_properties(${TESTNAME} PROPERTIES PROCESSORS ${CORES})
          endforeach()
        endforeach()
      endforeach()
    endforeach()
  endforeach()

  foreach(CORES 2 3 8)
    foreach(SEED RANGE 1 20)
      MATH(EXPR SEEDLOWER "${SEED} * ${CHUNK}")
      MATH(EXPR SEEDUPPER "((${SEED} + 1) * ${CHUNK}) - 1")
      SET (TESTNAME "func-sys-cowngc2_${CORES}_${SEEDLOWER}")
      add_test(${TESTNAME} func-sys-cowngc2 --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER})
    endforeach()
  endforeach()

  foreach(CORES ${CON_CORES})
    foreach(SEED RANGE 1 2)
      MATH(EXPR SEEDLOWER "${SEED} * 10")
      MATH(EXPR SEEDUPPER "((${SEED} + 1) * 10) - 1")
      SET (TESTNAME "func-con-cowngc2_${CORES}_${SEEDLOWER}")
      add_test(${TESTNAME} func-con-cowngc2 --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER})
      set_tests_properties(${TESTNAME} PROPERTIES PROCESSORS ${CORES})
    endforeach()
  endforeach()

  foreach(CORES 2 3 8)
    foreach(SEED RANGE 1 80)
      MATH(EXPR SEEDLOWER "${SEED} * ${CHUNK}")
      MATH(EXPR SEEDUPPER "((${SEED} + 1) * ${CHUNK}) - 1")
      SET (TESTNAME "func-sys-cowngc3_${CORES}_${SEEDLOWER}")
      add_test(${TESTNAME} func-sys-cowngc3 --allow_leaks --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER})
    endforeach()
  endforeach()

  foreach(CORES ${CON_CORES})
    foreach(SEED RANGE 1 8)
      MATH(EXPR SEEDLOWER "${SEED} * 10")
      MATH(EXPR SEEDUPPER "((${SEED} + 1) * 10) - 1")
      SET (TESTNAME "func-con-cowngc3_${CORES}_${SEEDLOWER}")
      add_test(${TESTNAME} func-con-cowngc3 --allow_leaks --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER})
      set_tests_properties(${TESTNAME} PROPERTIES PROCESSORS ${CORES})
    endforeach()
  endforeach()

  foreach(CORES 2 3 8)
    foreach(PHILOSOPHERS 2 3 4 10)
      foreach(HUNGER 1 2 3 10 50)
        foreach(FORKS 2 3 5)
          if (${FORKS} LESS_EQUAL ${PHILOSOPHERS})
            foreach(SEED RANGE 1 20)
              MATH(EXPR SEEDLOWER "${SEED} * ${CHUNK}")
              MATH(EXPR SEEDUPPER "((${SEED} + 1) * ${CHUNK}) - 1")
              SET (TESTNAME "func-sys-diningphilosophers_${CORES}_${PHILOSOPHERS}_${HUNGER}_${FORKS}_${SEEDLOWER}")
              add_test(${TESTNAME} func-sys-diningphilosophers --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER} --philosophers ${PHILOSOPHERS} --hunger ${HUNGER} --forks ${FORKS})
            endforeach()
          endif()
        endforeach()
      endforeach()
    endforeach()
  endforeach()

  foreach(CORES 4)
    foreach(PHILOSOPHERS 100)
      foreach(HUNGER 5)
        foreach(FORKS 10)
          if (${FORKS} LESS_EQUAL ${PHILOSOPHERS})
            foreach(SEED RANGE 1 20)
              MATH(EXPR SEEDLOWER "${SEED} * ${CHUNK}")
              MATH(EXPR SEEDUPPER "((${SEED} + 1) * ${CHUNK}) - 1")
              SET (TESTNAME "func-sys-diningphilosophers_${CORES}_${PHILOSOPHERS}_${HUNGER}_${FORKS}_${SEEDLOWER}")
              add_test(${TESTNAME} func-sys-diningphilosophers --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER} --philosophers ${PHILOSOPHERS} --hunger ${HUNGER} --forks ${FORKS})
            endforeach()
          endif()
        endforeach()
      endforeach()
    endforeach()
  endforeach()


  foreach(CORES ${CON_CORES})
    foreach(PHILOSOPHERS 2 3 4 10)
      foreach(HUNGER 1 2 3 10 100)
        foreach(FORKS 2 3 5)
          if (${FORKS} LESS_EQUAL ${PHILOSOPHERS})
            foreach(SEED RANGE 1 2)
              MATH(EXPR SEEDLOWER "${SEED} * 10")
              MATH(EXPR SEEDUPPER "((${SEED} + 1) * 10) - 1")
              SET (TESTNAME "func-con-diningphilosophers_${CORES}_${PHILOSOPHERS}_${HUNGER}_${FORKS}_${SEEDLOWER}")
              add_test(${TESTNAME} func-con-diningphilosophers --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER} --philosophers ${PHILOSOPHERS} --hunger ${HUNGER} --forks ${FORKS})
              set_tests_properties(${TESTNAME} PROPERTIES PROCESSORS ${CORES})
            endforeach()
          endif()
        endforeach()
      endforeach()
    endforeach()
  endforeach()

  foreach(CORES 2 3 8)
    foreach(SEED RANGE 1 20)
      MATH(EXPR SEEDLOWER "${SEED} * ${CHUNK}")
      MATH(EXPR SEEDUPPER "((${SEED} + 1) * ${CHUNK}) - 1")
      SET (TESTNAME "func-sys-noticeboard_${CORES}_${SEEDLOWER}")
      add_test(${TESTNAME} func-sys-noticeboard --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER})
    endforeach()
  endforeach()

  foreach(CORES 2 20)
    foreach(SEED RANGE 1 20)
      MATH(EXPR SEEDLOWER "${SEED} * ${CHUNK}")
      MATH(EXPR SEEDUPPER "((${SEED} + 1) * ${CHUNK}) - 1")
      SET (TESTNAME "func-sys-cown_weak_ref_${CORES}_${SEEDLOWER}")
      add_test(${TESTNAME} func-sys-cown_weak_ref --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER})
    endforeach()
  endforeach()

  foreach(CORES 2 3 8)
    foreach(SEED RANGE 1 20)
      MATH(EXPR SEEDLOWER "${SEED} * ${CHUNK}")
      MATH(EXPR SEEDUPPER "((${SEED} + 1) * ${CHUNK}) - 1")
      SET (TESTNAME "func-sys-ext_ref_freeze_${CORES}_${SEEDLOWER}")
      add_test(${TESTNAME} func-sys-ext_ref_freeze --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER})
    endforeach()
  endforeach()

  foreach(CORES 2 3 8)
    foreach(SEED RANGE 1 20)
      MATH(EXPR SEEDLOWER "${SEED} * ${CHUNK}")
      MATH(EXPR SEEDUPPER "${SEEDLOWER} + 3")
      SET (TESTNAME "func-sys-runtimepause_${CORES}_${SEEDLOWER}")
      add_test(${TESTNAME} func-sys-runtimepause --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER})
    endforeach()
  endforeach()

  foreach(CORES 2 3 8)
    foreach(SEED RANGE 1 20)
      MATH(EXPR SEEDLOWER "${SEED} * ${CHUNK}")
      MATH(EXPR SEEDUPPER "((${SEED} + 1) * ${CHUNK}) - 1")
      SET (TESTNAME "func-sys-fair${CORES}_${SEEDLOWER}")
      add_test(${TESTNAME} func-sys-fair --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER})
    endforeach()
  endforeach()

  foreach(CORES 1 2 3 8)
    foreach(SEED RANGE 1 20)
      MATH(EXPR SEEDLOWER "${SEED} * ${CHUNK}")
      MATH(EXPR SEEDUPPER "((${SEED} + 1) * ${CHUNK}) - 1")
      SET (TESTNAME "func-sys-notify${CORES}_${SEEDLOWER}")
      add_test(${TESTNAME} func-sys-notify --cores ${CORES} --seed ${SEEDLOWER} --seed_upper ${SEEDUPPER})
    endforeach()
  endforeach()
endif()